package com.zzyl.job;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.zzyl.constant.CacheConstants;
import com.zzyl.dto.AlertNotifyDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.enums.AlertDataType;
import com.zzyl.enums.LocationType;
import com.zzyl.mapper.AlertDataMapper;
import com.zzyl.mapper.AlertRuleMapper;
import com.zzyl.mapper.DeviceMapper;
import com.zzyl.mapper.UserMapper;
import com.zzyl.vo.DeviceDataVo;
import com.zzyl.websocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
@Component
public class DeviceAlertJob {
    //设备维护人员的角色名称
    @Value("${zzyl.alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;
    //超级管理员的角色名称
    @Value("${zzyl.alert.managerRole}")
    private String managerRole;
    @Autowired
    private AlertRuleMapper alertRuleMapper;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private AlertDataMapper alertDataMapper;
    @Autowired
    private WebSocketServer webSocketServer;


    @Scheduled(cron = "0 0/1 * * * ?")
    public void deviceAlert() {
        //1.查询所有的报警规则, 如果为空直接返回
        log.info("查询所有的报警规则");
        List<AlertRule> ruleList = alertRuleMapper.selectALL();
        if (CollUtil.isEmpty(ruleList)) {
            log.info("规则不存在,直接返回");
            return;
        }

        //2. 遍历报警规则, 查询规则对应的设备数据
        log.info("遍历报警规则, 查询规则对应的设备数据");
        ruleList.forEach(alertRule -> {
            List<Object> deviceDataList = new ArrayList<>();//当前规则下的所有的设备的上报数据
            if (StrUtil.equals("-1", alertRule.getIotId())) {
                //所有设备
                List<Object> temp = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA_KEY + alertRule.getProductKey());
                if (CollUtil.isNotEmpty(temp)) {
                    deviceDataList.addAll(temp);
                }
            } else {
                //指定设备
                Object deviceData = redisTemplate.opsForHash().get(CacheConstants.IOT_DEVICE_LAST_DATA_KEY + alertRule.getProductKey(), alertRule.getIotId());
                if (ObjectUtil.isNotNull(deviceData)) {
                    deviceDataList.add(deviceData);
                }
            }
            if (CollUtil.isEmpty(deviceDataList)) {
                log.info("设备数据不存在,进行下一个规则的校验");
                return;
            }
            //3.遍历每一个设备数据,根据报警规则进行判断
            log.info("遍历当前规则下的设备数据,根据报警规则进行判断");
            deviceDataList.forEach(deviceDataStr -> {
                // deviceDataStr 是从Redis中取出来的JSON字符串==>[{},{}]
                List<DeviceDataVo> list = JSONUtil.toList(deviceDataStr.toString(), DeviceDataVo.class);
                list.forEach(deviceDataVo -> {
                    //处理当前规则下的设备数据
                    processAlertData(deviceDataVo, alertRule);//处理设备报警数据
                });
            });

        });


    }

    /**
     * 处理设备数据
     *
     * @param deviceData 设备数据
     * @param alertRule  告警规则
     */
    private void processAlertData(DeviceDataVo deviceData, AlertRule alertRule) {
        log.info("当前设备数据" + deviceData + "当前规则" + alertRule);
        //3.1 上报数据上报超过一分钟为历史数据,放弃处理
        if (ObjectUtil.isNotNull(deviceData.getCreateTime())
                && deviceData.getAlarmTime().isBefore(LocalDateTime.now().minusMinutes(1))) {
            log.info("上报数据上报超过一分钟为历史数据,放弃处理");
            return;
        }

        //3.2 判断数据上报时间是否在 报警生效时间段范围内, 不在则放弃处理
        log.info("=========> 3.2、判断数据上报时间是否在报警生效时间段范围内");
        if (ObjectUtil.isNotNull(alertRule.getAlertEffectivePeriod())
                && ObjectUtil.isNotNull(deviceData.getAlarmTime())) {
            LocalTime start = LocalTime.parse(alertRule.getAlertEffectivePeriod().split("~")[0]);
            LocalTime end = LocalTime.parse(alertRule.getAlertEffectivePeriod().split("~")[1]);
            if (deviceData.getAlarmTime().toLocalTime().isBefore(start)
                    || deviceData.getAlarmTime().toLocalTime().isAfter(end)) {
                log.info("数据上报时间不在报警生效时间段范围内，放弃处理");
                return;
            }
        }

        //3.3 判断数据是否满足报警规则,满足则发送告警消息
        String alertTriggerCountKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + deviceData.getIotId() +":"+deviceData.getFunctionId()+":"+alertRule.getId();
        log.info("数据满足报警规则,发送告警消息");
        String expression = deviceData.getDataValue() + " " + alertRule.getOperator() + " " + alertRule.getValue();
        if (!evaluateExpression(expression)) {
            log.info("数据不满足报警规则,放弃处理");
            //删除设备的连续累计报警数
            redisTemplate.delete(alertTriggerCountKey );
            return;
        }

        //3.4、判断当前设备是否处于报警沉默周期，如果还在沉默周期，放弃处理
        log.info("=========> 3.4、判断当前设备是否处于报警沉默周期");
        String alertSilentKey = CacheConstants.ALERT_SILENT_PREFIX + deviceData.getFunctionId() +":"+deviceData.getFunctionId()+":"+alertRule.getId();
        if(Boolean.TRUE.equals(redisTemplate.hasKey(alertSilentKey))){
            log.info("=========> 当前设备处于报警沉默周期，放弃处理");
            return;
        }
        //3.5、判断连续报警次数是否达到持续周期的阈值
        log.info("=========> 3.5、判断连续报警次数是否达到持续周期的阈值");
        Long count = redisTemplate.opsForValue().increment(alertSilentKey);//
        if(count < alertRule.getDuration()){
            log.info("=========> 当前设备连续报警次数未达到持续周期的阈值，放弃处理");
            return;
        }
        //3.6、发送告警消息
        log.info("=========> 3.6、发送告警消息");
        generateAlertData(deviceData, alertRule);

    }

    /**
     * 处理告警数据
     * @param deviceData
     * @param alertRule
     */
    private void generateAlertData(DeviceDataVo deviceData, AlertRule alertRule) {
    log.info("=========> 3.6、处理告警消息");
        //1、删除Redis中连续累计报警数
        String alertTriggerCountKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + deviceData.getFunctionId() +":"+deviceData.getFunctionId()+":"+alertRule.getId();
        redisTemplate.delete(alertTriggerCountKey);

        //2.存储报警沉默周期到Redis中
        String alertSilentKey = CacheConstants.ALERT_SILENT_PREFIX + deviceData.getFunctionId() +":"+deviceData.getFunctionId()+":"+alertRule.getId();
        redisTemplate.opsForValue().set(alertSilentKey,JSONUtil.toJsonStr(deviceData),alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);

        //3.存储报警数据到数据库中
        //3.1 获取告警消息的消费者[告知人]
        Set<Long> alertConsumer= new HashSet<>();
        if (alertRule.getAlertDataType().equals(AlertDataType.ELDER.getOrdinal())){
            //设备 ==> 关联的老人
            if (deviceData.getLocationType().equals(LocationType.PORTABLE.getOrdinal())){
                //情况一: 设备的 location_type 是0 随身设备, 则 binding_location 是老人Id
                List<Long> temp=deviceMapper.selectNurserByIotId2Portable(deviceData.getIotId());
                alertConsumer.addAll(temp);
            }else {
                //情况二: 设备的 location_type 是1 固定设备,且physical_location_type是2 床位, 则 binding_location 是床位Id
                List<Long> temp=deviceMapper.selectByIotId2Fixed(deviceData.getIotId());
                alertConsumer.addAll(temp);
            }
        }else {
            // 关联的老人==> 护理人员
            List<Long> temp=userMapper.selectUserByRoleName(deviceMaintainerRole);
            alertConsumer.addAll(temp);
        }
        List<Long> temp=userMapper.selectUserByRoleName(managerRole);
        alertConsumer.addAll(temp);

        if(CollUtil.isNotEmpty(alertConsumer)){
            //3.2、存储数据库
            log.info("=========> 3.6.3、存储报警数据存入数据库,告知人集合{}",alertConsumer);
            String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警",
                    alertRule.getFunctionName(),
                    alertRule.getOperator(),
                    alertRule.getValue(),
                    alertRule.getDuration());

            AlertData alertData = BeanUtil.toBean(deviceData, AlertData.class);
            alertData.setAlertRuleId(alertRule.getId());
            alertData.setAlertReason(alertReason);
            alertData.setType(alertRule.getAlertDataType());
            alertData.setStatus(0);//状态，0：待处理，1：已处理,

            //循环遍历消费者id集合，每个消费者创建一个对应的alertData数据，存入数据库
            List<AlertData> list = alertConsumer.stream().map(id -> {
                AlertData dbAlertData = BeanUtil.toBean(alertData, AlertData.class);
                dbAlertData.setUserId(id);//接收人ID
                return dbAlertData;
            }).collect(Collectors.toList());

            alertDataMapper.batchInsert(list);
            AlertNotifyDto alertNotifyDto = BeanUtil.copyProperties(alertData, AlertNotifyDto.class);
            alertNotifyDto.setFunctionName(alertRule.getFunctionName());
            alertNotifyDto.setAlertDataType(alertData.getType());
            alertNotifyDto.setNotifyType(1);
            log.info("=========> 3.6.3、基于websocket通信，发送告警信息给对应接收人（{}）的客户端浏览器",alertConsumer);

            webSocketServer.sendMessageToConsumer(alertNotifyDto,new ArrayList<>(alertConsumer));

            log.info("=========> ####################### END ##################");
        }

    }

    private  boolean evaluateExpression(String expression) {
        expression = expression.replace(" ", "");
        Pattern pattern = Pattern.compile("(-?\\d+(\\.\\d+)?)\\s*([<>]=?|==|!=)\\s*(-?\\d+(\\.\\d+)?)");
        Matcher matcher = pattern.matcher(expression);

        if (matcher.matches()) {
            double num1 = Double.parseDouble(matcher.group(1));
            String operator = matcher.group(3);
            double num2 = Double.parseDouble(matcher.group(4));

            switch (operator) {
                case ">":
                    return num1 > num2;
                case ">=":
                    return num1 >= num2;
                case "<":
                    return num1 < num2;
                case "<=":
                    return num1 <= num2;
                case "==":
                    return num1 == num2;
                case "!=":
                    return num1 != num2;
                default:
                    throw new IllegalArgumentException("Unsupported operator: " + operator);
            }
        } else {
            throw new IllegalArgumentException("Invalid expression format: "+expression);
        }
    }
}